/*
OpenDocumentMetadata is an Object representing the metadata in an
OpenDocument file.
Copyright (C) 2005  J. David Eisenberg
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
Author: J. David Eisenberg
Contact: catcode@catcode.com
 */
package com.catcode.odf;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

import java.text.ParseException;
import java.text.SimpleDateFormat;

import java.util.Date;
import java.util.Hashtable;

/**
 * OpenDocumentMetadata describes the content of an
 * OASIS Open Document Format meta-information file.
 *
 * The declarations of the fields in this class closely
 * mirror the element and attribute names in the meta.xml
 * file.  For example, the <code>printedBy</code> field
 * contains the information that comes from the
 * <code>&lt;meta:printed-by&gt;</code> element.  This
 * is no accident--it allows code that analyzes a
 * meta.xml file to use reflection to call the methods
 * for setting the fields in this class.
 *
 *	@author		J. David Eisenberg
 *	@version	0.3, 2005-11-29
 */
public class OpenDocumentMetadata {
	private String generator;			// meta:generator
	private String title;				// dc:title
	private String description;			// dc:description
	private String subject;				// dc:subject
	private String keyword;				// meta:keyword
	private String initialCreator;		// meta:initial-creator
	private String creator;				// dc:creator
	private String printedBy;			// meta:printed-by
	private Date creationDate;			// meta:creation-date
	private Date date;					// dc:date;
	private Date printDate;				// meta:print-date
	private String language;			// dc:language;
	private Integer editingCycles;		// meta:editing-cycles
	private Duration editingDuration;	// meta:editing-duration

	// attributes from <meta:document-statistic>
	private Integer pageCount;			// meta:page-count
	private Integer tableCount;			// meta:table-count
	private Integer drawCount;			// meta:draw-count
	private Integer imageCount;			// meta:image-count
	private Integer oleObjectCount;		// meta:ole-object-count
	private Integer paragraphCount;		// meta:paragraph-count
	private Integer wordCount;			// meta:word-count
	private Integer characterCount;		// meta:character-count
	private Integer frameCount;			// meta:frame-count
	private Integer sentenceCount;		// meta:sentence-count
	private Integer syllableCount;		// meta:syllable-count
	private Integer nonWhitespaceCharacterCount;
	// meta:non-whitespace-character-count
	private Integer rowCount;			// meta:row-count
	private Integer cellCount;			// meta:cell-count
	private Integer objectCount;		// meta:object-count
	private static final SimpleDateFormat isoDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
	protected Hashtable<String,Object> userDefined = new Hashtable<String, Object>(10);

	/**
	 * Return the generator meta information.
	 *
	 * @return the generator.
	 */
	public String getGenerator() {
		return this.generator;
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;meta:generator&gt;</code> to given value.
	 *
	 * @param generator the generator.
	 */
	public void setGenerator(String generator) {
		this.generator = generator;
	}

	/**
	 * Return the document title.
	 *
	 * @return the title.
	 */
	public String getTitle() {
		return this.title;
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;dc:title&gt;</code> to given value.
	 *
	 * @param title the title.
	 */
	public void setTitle(String title) {
		this.title = title;
	}

	/**
	 * Return the document description.
	 *
	 * @return the description.
	 */
	public String getDescription() {
		return this.description;
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;dc:description&gt;</code> to given value.
	 *
	 * @param description the description.
	 */
	public void setDescription(String description) {
		this.description = description;
	}

	/**
	 * Return the document subject.
	 *
	 * @return the subject.
	 */
	public String getSubject() {
		return this.subject;
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;dc:subject&gt;</code> to given value.
	 *
	 * @param subject the subject.
	 */
	public void setSubject(String subject) {
		this.subject = subject;
	}

	/**
	 * Return the list of keywords for this document.
	 *
	 * @return a string of keyword information.
	 */
	public String getKeyword() {
		return this.keyword;
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;meta:keyword&gt;</code> to given value.
	 * <p>Since there can be multiple <code>&lt;meta:keyword&gt;</code>
	 * elements, this method joins them together into a single
	 * string with commas between keywords.
	 *
	 * @param keyword the keyword.
	 */
	public void setKeyword(String keyword) {
		if (this.keyword == null || this.keyword.equals("")) {
			this.keyword = keyword;
		} else {
			this.keyword += ("," + keyword);
		}
	}

	/**
	 * Return the document's initial creator.
	 *
	 * @return the creator.
	 */
	public String getInitialCreator() {
		return this.initialCreator;
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;meta:initial-creator&gt;</code> to given value.
	 *
	 * @param initialCreator the initial creator.
	 */
	public void setInitialCreator(String initialCreator) {
		this.initialCreator = initialCreator;
	}

	/**
	 * Return the last editor of the document.
	 *
	 * @return the last editor.
	 */
	public String getCreator() {
		return this.creator;
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;dc:creator&gt;</code> to given value.
	 * This is actually the last person to edit the document.
	 *
	 * @param creator the last editor.
	 */
	public void setCreator(String creator) {
		this.creator = creator;
	}

	/**
	 * Return the last person to print the document.
	 *
	 * @return the subject.
	 */
	public String getPrintedBy() {
		return this.printedBy;
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;meta:printed-by&gt;</code> to given value.
	 *
	 * @param printedBy the last person to print document.
	 */
	public void setPrintedBy(String printedBy) {
		this.printedBy = printedBy;
	}

	/**
	 * Return the document's creation date and time.
	 * In the XML, date and time are in the format
	 * <code>YYYY-MM-DDThh:mm:ss</code>.
	 * @return the creation date and time.
	 */
	public Date getCreationDate() {
		return this.creationDate;
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;meta:creation-date&gt;</code> to given value.
	 *
	 * @param creationDate the creationDate.
	 */
	public void setCreationDate(Date creationDate) {
		this.creationDate = creationDate;
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;meta:creation-date&gt;</code> to given value.
	 *
	 * @param strCreationDate the creation date
	 * in the form <code>yyyy-mm-ddTHH:mm:ss</code>.
	 */
	public void setCreationDate(String strCreationDate) {
		try {
			creationDate = isoDate.parse(strCreationDate);
		} catch (ParseException e) {
			creationDate = null;
		}
	}

	/**
	 * Return the document's last edit date and time.
	 * In the XML, date and time are in the format
	 * <code>YYYY-MM-DDThh:mm:ss</code>.
	 * @return the date and time of last edit.
	 */
	public Date getDate() {
		return this.date;
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;dc:date&gt;</code> (the last editing date) to given value.
	 *
	 * @param date the last date document was edited.
	 */
	public void setDate(Date date) {
		this.date = date;
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;dc:date&gt;</code> (the last editing date) to given value.
	 *
	 * @param strDate the last editing date 
	 * in form <code>yyyy-mm-ddTHH:mm:ss</code>.
	 */
	public void setDate(String strDate) {
		try {
			date = isoDate.parse(strDate);
		} catch (ParseException e) {
			date = null;
		}
	}

	/**
	 * Return the date and time the document was last printed.
	 * In the XML, date and time are in the format
	 * <code>YYYY-MM-DDThh:mm:ss</code>.
	 * @return the last print date and time.
	 */
	public Date getPrintDate() {
		return this.printDate;
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;meta:print-date&gt;</code> to given value.
	 *
	 * @param printDate the last editing date.
	 */
	public void setPrintDate(Date printDate) {
		this.printDate = printDate;
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;meta:print-date&gt;</code> to given value.
	 *
	 * @param strPrintDate the last printed date
	 * in form <code>yyyy-mm-ddTHH:mm:ss</code>.
	 */
	public void setPrintDate(String strPrintDate) {
		try {
			printDate = isoDate.parse(strPrintDate);
		} catch (ParseException e) {
			printDate = null;
		}
	}

	/**
	 * Return the document's language.
	 * The language consists of a two or three letter Language Code
	 * taken from the ISO 639 standard optionally followed by a
	 * hyphen (-) and a two-letter Country Code taken from the
	 * ISO 3166 standard.
	 *
	 * @return the language.
	 */
	public String getLanguage() {
		return this.language;
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;dc:language&gt;</code> to given value.
	 *
	 * @param language the language as a two-letter code, optionally followed by
	 * hyphen and two-letter country code.
	 */
	public void setLanguage(String language) {
		this.language = language;
	}

	/**
	 * Return the number of editing cycles for this document.
	 *
	 * @return the number of editing cycles.
	 */
	public int getEditingCycles() {
		return this.editingCycles.intValue();
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;meta:editing-cycles&gt;</code> to given value.
	 *
	 * @param editingCycles the number of editing cycles.
	 */
	public void setEditingCycles(int editingCycles) {
		this.editingCycles = new Integer(editingCycles);
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;meta:editing-cycles&gt;</code> to given value.
	 *
	 * @param strEditingCycles the number of editing cycles.
	 */
	public void setEditingCycles(String strEditingCycles) {
		this.editingCycles = new Integer(strEditingCycles);
	}

	/**
	 * Return the total time spent editing the document.
	 * In the XML, duration is in the form
	 * <code>PnYnMnDTnHnMnS</code>.
	 * @return the creation date and time.
	 */
	public Duration getEditingDuration() {
		return this.editingDuration;
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;meta:editing-duration&gt;</code> to given value.
	 *
	 * @param editingDuration the total editing time.
	 */
	public void setEditingDuration(Duration editingDuration) {
		this.editingDuration = editingDuration;
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;meta:editing-duration&gt;</code> to given value.
	 *
	 * @param strEditingDuration the total editing time
	 * in the form <code>PnYnMnDTnHnMnS</code>.
	 */
	public void setEditingDuration(String strEditingDuration) {
		this.editingDuration = Duration.parseDuration(strEditingDuration);
	}

	/**
	 * Return the total number of pages in the document.
	 *
	 * @return the number of pages.
	 */
	public int getPageCount() {
		return this.pageCount.intValue();
	}

	/**
	 * Set the information corresponding to
	 * <code>meta:page-count</code> to given value.
	 *
	 * @param pageCount the total number of pages.
	 */
	public void setPageCount(int pageCount) {
		this.pageCount = new Integer(pageCount);
	}

	/**
	 * Return the total number of tables in this text or spreadsheet document.
	 *
	 * @return the number of tables.
	 */
	public int getTableCount() {
		return this.tableCount.intValue();
	}

	/**
	 * Set the information corresponding to
	 * <code>meta:table-count</code> to given value.
	 *
	 * @param tableCount the total number of tables.
	 */
	public void setTableCount(int tableCount) {
		this.tableCount = new Integer(tableCount);
	}

	/**
	 * Return the total number of drawings in the document.
	 *
	 * @return the number of drawings.
	 */
	public int getDrawCount() {
		return this.drawCount.intValue();
	}

	/**
	 * Set the information corresponding to
	 * <code>meta:draw-count</code> to given value.
	 *
	 * @param drawCount the total number of drawings.
	 */
	public void setDrawCount(int drawCount) {
		this.drawCount = new Integer(drawCount);
	}

	/**
	 * Return the total number of images in the document.
	 *
	 * @return the number of images.
	 */
	public int getImageCount() {
		return this.imageCount.intValue();
	}

	/**
	 * Set the information corresponding to
	 * <code>meta:image-count</code> to given value.
	 *
	 * @param imageCount the total number of images.
	 */
	public void setImageCount(int imageCount) {
		this.imageCount = new Integer(imageCount);
	}

	/**
	 * Return the total number of OLE objects in the document.
	 *
	 * @return the number of OLE objects.
	 */
	public int getOleObjectCount() {
		return this.oleObjectCount.intValue();
	}

	/**
	 * Set the information corresponding to
	 * <code>meta:ole-object-count</code> to given value.
	 *
	 * @param oleObjectCount the total number of OLE objects.
	 */
	public void setOleObjectCount(int oleObjectCount) {
		this.oleObjectCount = new Integer(oleObjectCount);
	}

	/**
	 * Return the total number of paragraphs in the document.
	 *
	 * @return the number of paragraphs.
	 */
	public int getParagraphCount() {
		return this.paragraphCount.intValue();
	}

	/**
	 * Set the information corresponding to
	 * <code>meta:paragraph-count</code> to given value.
	 *
	 * @param paragraphCount the total number of paragraphs.
	 */
	public void setParagraphCount(int paragraphCount) {
		this.paragraphCount = new Integer(paragraphCount);
	}

	/**
	 * Return the total number of words in the document.
	 *
	 * @return the number of words.
	 */
	public int getWordCount() {
		return this.wordCount.intValue();
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;meta:wod-count&gt;</code> to given value.
	 *
	 * @param wordCount the total number of words.
	 */
	public void setWordCount(int wordCount) {
		this.wordCount = new Integer(wordCount);
	}

	/**
	 * Return the total number of characters in the document.
	 *
	 * @return the number of images.
	 */
	public int getCharacterCount() {
		return this.characterCount.intValue();
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;meta:character-count&gt;</code> to given value.
	 *
	 * @param characterCount the total number of characters.
	 */
	public void setCharacterCount(int characterCount) {
		this.characterCount = new Integer(characterCount);
	}

	/**
	 * Return the total number of frames in the document.
	 *
	 * @return the number of frames.
	 */
	public int getFrameCount() {
		return this.frameCount.intValue();
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;meta:frame-count&gt;</code> to given value.
	 *
	 * @param frameCount the total number of frames.
	 */
	public void setFrameCount(int frameCount) {
		this.frameCount = new Integer(frameCount);
	}

	/**
	 * Return the total number of sentences in the document.
	 *
	 * @return the number of sentences.
	 */
	public int getSentenceCount() {
		return this.sentenceCount.intValue();
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;meta:sentence-count&gt;</code> to given value.
	 *
	 * @param sentenceCount the total number of sentences.
	 */
	public void setSentenceCount(int sentenceCount) {
		this.sentenceCount = new Integer(sentenceCount);
	}

	/**
	 * Return the total number of syllables in the document.
	 *
	 * @return the number of syllables.
	 */
	public int getSyllableCount() {
		return this.syllableCount.intValue();
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;meta:syllable-count&gt;</code> to given value.
	 *
	 * @param syllableCount the total number of syllables.
	 */
	public void setSyllableCount(int syllableCount) {
		this.syllableCount = new Integer(syllableCount);
	}

	/**
	 * Return the total number of non-whitespace characters in the document.
	 *
	 * @return the number of non-whitespace characters.
	 */
	public int getNonWhitespaceCharacterCount() {
		return this.nonWhitespaceCharacterCount.intValue();
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;meta:non-whitespace-character-count&gt;</code> to given value.
	 *
	 * @param nonWhitespaceCharacterCount the total number of non-whitespace
	 * characters.
	 */
	public void setNonWhitespaceCharacterCount(int nonWhitespaceCharacterCount) {
		this.nonWhitespaceCharacterCount = new Integer(
				nonWhitespaceCharacterCount);
	}

	/**
	 * Return the total number of rows in the document.
	 * This applies to text documents, strangely, not spreadsheets.
	 *
	 * @return the number of images.
	 */
	public int getRowCount() {
		return this.rowCount.intValue();
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;meta:row-count&gt;</code> to given value.
	 *
	 * @param rowCount the total number of rows.
	 */
	public void setRowCount(int rowCount) {
		this.rowCount = new Integer(rowCount);
	}

	/**
	 * Return the total number of cells in this spreadsheet document.
	 *
	 * @return the number of cells.
	 */
	public int getCellCount() {
		return this.cellCount.intValue();
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;meta:cell-count&gt;</code> to given value.
	 *
	 * @param cellCount the total number of cells.
	 */
	public void setCellCount(int cellCount) {
		this.cellCount = new Integer(cellCount);
	}

	/**
	 * Return the total number of graphic objects in this spreadsheet
	 * or graphics document.
	 *
	 * @return the number of graphic objects.
	 */
	public int getObjectCount() {
		return this.objectCount.intValue();
	}

	/**
	 * Set the information corresponding to
	 * <code>&lt;meta:object-count&gt;</code> to given value.
	 *
	 * @param objectCount the total number of graphic objects.
	 */
	public void setObjectCount(int objectCount) {
		this.objectCount = new Integer(objectCount);
	}

	/**
	 * Return the user-defined meta information.
	 *
	 * The hashtable key is a String giving the name of this
	 * user-defined information; the value is an Object which can either be
	 * Double, Date (for date), Duration (for time)
	 * Boolean, or String.
	 *
	 * @return the user-defined information.
	 */
	public Hashtable getUserDefined() {
		return this.userDefined;
	}

	/**
	 * Set the information corresponding to all
	 * <code>&lt;meta:user-defined&gt;</code> elements.
	 *
	 * @param userDefined all user-defined meta-information.
	 */
	public void setUserDefined(Hashtable<String,Object> userDefined) {
		this.userDefined = userDefined;
	}

	/**
	 * Set user-defined info for given name to an integer value.
	 *
	 * @param name the name for this user-defined info
	 * @param value the integer value
	 */
	public void setUserDefined(String name, int value) {
		userDefined.put(name, new Double(value));
	}

	/**
	 * Set user-defined info for given name to a double value.
	 *
	 * @param name the name for this user-defined info
	 * @param value the double value
	 */
	public void setUserDefined(String name, double value) {
		userDefined.put(name, new Double(value));
	}

	/**
	 * Set user-defined info for given name to a boolean value.
	 *
	 * @param name the name for this user-defined info
	 * @param value the boolean value
	 */
	public void setUserDefined(String name, boolean value) {
		userDefined.put(name, new Boolean(value));
	}

	/**
	 * Set user-defined info for given name to an Object value.
	 * This is the catch-all that handles Dates, Durations,
	 * and true Double, Boolean, and String.
	 *
	 * @param name the name for this user-defined info
	 * @param value the object value
	 */
	public void setUserDefined(String name, Object value) {
		userDefined.put(name, value);
	}

	/**
	 * Returns an Object for a field by name.
	 *
	 * @param fieldName 
	 * @return the field value for the given name, null if no such field.
	 */
	public Object getFieldByName(String fieldName) {
		Field theField;
		Object result = null;
		try {
			theField = OpenDocumentMetadata.class.getDeclaredField(fieldName);
			result = theField.get(this);
		} catch (Exception e) {
		// do nothing; result is already null
		}
		return result;
	}

	/**
	 * Returns a string representation of this
	 * <code>OpenDocumentMetadata</code>.
	 * This method is intended to be used only for debugging purposes. 
	 * It lists all the non-static fields of the object in the order
	 * returned by <code>getDeclaredFields()</code>,
	 * with their values converted via <code>toString()</code>.
	 * @return a string representation of
	 * this <code>OpenDocumentMetadata</code>.
	 */
	@Override
	public String toString() {
		Field[] field = OpenDocumentMetadata.class.getDeclaredFields();
		int i;
		String result = "";
		String oneField;

		for (i = 0; i < field.length; i++) {
			if (!Modifier.isStatic(field[i].getModifiers())) {
				try {
					if (field[i].get(this) != null) {
						oneField = field[i].get(this).toString();
					} else {
						oneField = "null";
					}
				} catch (IllegalAccessException e) {
					oneField = "**cannot access**";
				}
				result += field[i].getName() + ": " +
						oneField + "\n";
			}
		}
		return result;
	}
}

